from vn_local.app.market_maker import CtaEngine
from vn_local.app.market_maker.template import CtaTemplate
from vn_local.app.market_maker.base import StopOrder
from vn_local.trader.object import (
    MonitorInfo,
    TickData,
    BarData,
    OrderTradeData,
    PublicTradeData, SubscribeRequest, Exchange, Direction, Status, OrderType, AccountData
)
from vn_local.trader.utility import round_to, DotDict, load_json
from vn_local.trader.constant import WidenAlertType, AbnormalAlertType
import numpy as np
from typing import Dict
import time
from datetime import datetime
from collections import defaultdict


def find_changed_values(dict1, dict2):
    # 初始化一个空字典来存储变化的键值对
    changed_values = {}

    # 遍历第一个字典的所有键
    for key in dict1:
        # 获取当前时间戳
        timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")

        # 如果键是 'order_placement_params' 需要特殊处理
        if key == 'order_placement_params' and key in dict2:
            # 比较每一层的参数
            for i, (param1, param2) in enumerate(zip(dict1[key], dict2[key])):
                for side in ['buy_side', 'sell_side']:
                    for param_key in param1[side]:
                        if param1[side][param_key] != param2[side][param_key]:
                            if key not in changed_values:
                                changed_values[key] = []
                            changed_values[key].append({
                                'layer': param1['layer'],
                                'side': side,
                                'param': param_key,
                                'old_value': param1[side][param_key],
                                'new_value': param2[side][param_key],
                                'changed_at': timestamp
                            })
        # 对于其他键，如 'miu'，如果值发生变化
        elif key in dict2 and dict1[key] != dict2[key]:
            changed_values[key] = {
                'old_value': dict1[key],
                'new_value': dict2[key],
                'changed_at': timestamp  # 添加时间戳
            }

    return changed_values


class RiskStrategy(CtaTemplate):
    """"""
    author = "CC Chen"

    maker_strategy_name = ""
    taker_strategy_name = ""
    risk_strategy_name = ""
    broadcast_interval = 0
    risk_check_interval = 0
    setting_change_interval = 0
    abnormal_alerts_threshold = {}
    widen_alerts_threshold = {}

    widen_alerts = {}

    parameters = ["maker_strategy_name", "taker_strategy_name", "risk_strategy_name",
                  "broadcast_interval", "risk_check_interval",
                  "setting_change_interval", "abnormal_alerts_threshold",
                  "widen_alerts_threshold"]
    variables = ["widen_alerts"]

    def __init__(self, cta_engine, strategy_name, vt_symbol, setting):
        """"""
        super().__init__(cta_engine, strategy_name, vt_symbol, setting)

        self.broadcast_count = 0
        self.risk_check_count = 0
        self.setting_change_count = 0

        self.trade_count = 0
        self.tick_count = 0
        # self.open_order_count = 0
        # self.query_open_order_count = 0

        self.maker_open_orders_nums = 0
        self.maker_reject_orders_nums = 0
        # self.open_order_nums = 0

        self.latest_update_tick_time = None
        self.latest_update_trade_time = None

        self.inventory: Dict[str, AccountData] = defaultdict(int)
        self.symbol = self.vt_symbol.split(".")[0].split("-")[0]

        # self.strategies_setting = None

        self.maker_strategy_parameters = None
        self.maker_strategy_variables = None

        self.taker_strategy_parameters = None
        self.taker_strategy_variables = None

        self.risk_strategy_parameters = None
        self.risk_strategy_variables = None

        self.params_inited = False
        self.monitor_info = MonitorInfo()

        self.widen_alerts = {alert.value: False for alert in WidenAlertType}
        self.abnormal_alerts = {alert.value: False for alert in AbnormalAlertType}

    def on_init(self):
        """
        Callback when strategy is inited.
        """
        self.write_log("策略初始化")
        self.parameters = self.get_parameters()
        self.write_log(f"策略初始化,当前parameters = {self.parameters}")

    def on_start(self):
        """
        Callback when strategy is started.
        """
        self.write_log("策略启动")

    def on_tick(self, tick: TickData) -> None:

        if self.tick_count >= 100:
            msg = f"[on_strategy:on_tick]: symbol={tick.vt_symbol}, ts={tick.ts}, ask1={tick.ask_price_1}, bid1={tick.bid_price_1}"
            gateway = self.cta_engine.main_engine.get_gateway("BBC")
            gateway.write_log(msg, "bbc")
            self.tick_count = 0

        if tick.vt_symbol == self.vt_symbol:
            self.latest_update_tick_time = tick.ts

    def on_strategy_data(self, data: dict) -> None:
        # 初始化的时候把两个策略的参数记录一下
        if not self.params_inited:
            if data["strategy_name"] == self.maker_strategy_name:
                self.maker_strategy_parameters = data["parameters"]
                self.maker_strategy_variables = data["variables"]

            if data["strategy_name"] == self.taker_strategy_name:
                self.taker_strategy_parameters = data["parameters"]
                self.taker_strategy_variables = data["variables"]

            if data["strategy_name"] == self.risk_strategy_name:
                self.risk_strategy_parameters = data["parameters"]
                self.risk_strategy_variables = data["variables"]

            if self.maker_strategy_parameters and self.taker_strategy_parameters and self.risk_strategy_parameters:
                self.params_inited = True

        if "p_fit" not in data["variables"]:
            return
        # update p_fit
        self.monitor_info.latest_p_fit = data["variables"]["p_fit"]
        self.maker_open_orders_nums = data["variables"]["maker_open_orders_nums"]

    def on_public_trade(self, public_trade: PublicTradeData) -> None:
        # if self.heart_beat_count >= self.heart_beat_interval:
        #     msg = f"[on_public_trade]: symbol={public_trade.vt_symbol}, ts={public_trade.ts}, price={public_trade.price}"
        #     self.write_log(msg)
        if self.trade_count >= 100:
            msg = f"[on_strategy:on_public_trade]: symbol={public_trade.vt_symbol}, ts={public_trade.ts}, price={public_trade.price}"
            gateway = self.cta_engine.main_engine.get_gateway("BBC")
            gateway.write_log(msg, "bbc")

            self.trade_count = 0
        if public_trade.vt_symbol == self.vt_symbol:
            self.latest_update_trade_time = public_trade.ts

    def on_interval(self):
        self.broadcast_count += 1
        self.risk_check_count += 1
        self.setting_change_count += 1

        self.tick_count += 1
        self.trade_count += 1
        # self.query_open_order_count += 1

        if self.broadcast_count >= self.broadcast_interval:
            self.on_broadcast()
            self.broadcast_count = 0

        if self.risk_check_count >= self.risk_check_interval:
            self.on_risk_check()
            self.risk_check_count = 0

        if self.setting_change_count >= self.setting_change_interval:
            self.on_check_settings()
            self.setting_change_count = 0
        #
        # if self.query_open_order_count >= self.query_pending_order_interval:
        #     self.on_query_pending_order()
        #     self.query_open_order_count = 0

    def on_broadcast(self):
        for strategy_name, strategy in self.cta_engine.strategies.items():
            if strategy_name == self.maker_strategy_name:
                self.monitor_info.maker_long_trades = strategy.symbol_traded[Direction.LONG]
                self.monitor_info.maker_short_trades = strategy.symbol_traded[Direction.SHORT]
                self.monitor_info.maker_long_notional = strategy.symbol_traded_cost[Direction.LONG]
                self.monitor_info.maker_short_notional = strategy.symbol_traded_cost[Direction.SHORT]

                self.monitor_info.sell_pending_orders_nums = len(strategy.sell_orderid_order_dict)
                self.monitor_info.buy_pending_orders_nums = len(strategy.buy_orderid_order_dict)

            elif strategy_name == self.taker_strategy_name:
                self.monitor_info.taker_long_trades = strategy.symbol_traded[Direction.LONG]
                self.monitor_info.taker_short_trades = strategy.symbol_traded[Direction.SHORT]
                self.monitor_info.taker_long_notional = strategy.symbol_traded_cost[Direction.LONG]
                self.monitor_info.taker_short_notional = strategy.symbol_traded_cost[Direction.SHORT]
                self.monitor_info.taker_pending_orders_nums = len(strategy.strategy_order_trades)

        monitor_info_dict = self.monitor_info.to_json()
        self.write_log(msg=f"[on_broadcast] monitor_info_dict: {monitor_info_dict}")
        self.send_dingding(monitor_info_dict)

    def check_abnormal_conditions(self, t_now, coin_account, usdt_account, msg):
        """
        检查异常条件，触发异常时直接终止程序。
        """
        abnormal = False

        def append_abnormal(condition, alert_msg):
            nonlocal abnormal
            if condition:
                msg.append(alert_msg)
                abnormal = True

        append_abnormal(
            coin_account.available < self.abnormal_alerts_threshold[
                AbnormalAlertType.COIN_INVENTORY_INSUFFICIENT.value],
            f"insufficient coins to sell, currently available {coin_account.available}, "
            f"frozen {coin_account.frozen}, total {coin_account.total}"
        )
        append_abnormal(
            usdt_account.available < self.abnormal_alerts_threshold[
                AbnormalAlertType.USDT_INVENTORY_INSUFFICIENT.value],
            f"insufficient USDT to buy, currently available {usdt_account.available}, "
            f"frozen {usdt_account.frozen}, total {usdt_account.total}"
        )
        append_abnormal(
            self.maker_open_orders_nums >= self.abnormal_alerts_threshold[AbnormalAlertType.TOO_MANY_OPEN_ORDERS.value],
            f"maker open orders nums = {self.maker_open_orders_nums}, above the threshold {self.abnormal_alerts_threshold[AbnormalAlertType.TOO_MANY_OPEN_ORDERS.value]}"
        )
        append_abnormal(
            self.maker_reject_orders_nums >= self.abnormal_alerts_threshold[
                AbnormalAlertType.TOO_MANY_ORDER_REJECTION.value],
            f"maker reject order nums = {self.maker_reject_orders_nums}, above the threshold"
        )

        def check_time(latest_time, threshold, alert_type):
            if latest_time:
                converted_time = latest_time * 0.000001
                if t_now - converted_time > threshold:
                    last_update_time = datetime.fromtimestamp(converted_time)
                    return True, f"{alert_type} latest update time: {last_update_time}"
            return False, ""

        is_tick_abnormal, tick_msg = check_time(
            self.latest_update_tick_time,
            self.abnormal_alerts_threshold[AbnormalAlertType.BBO_OUT_OF_DATE.value], "tick"
        )
        is_trade_abnormal, trade_msg = check_time(
            self.latest_update_trade_time,
            self.abnormal_alerts_threshold[AbnormalAlertType.PUBLIC_TRADE_OUT_OF_DATE.value], "trade"
        )

        append_abnormal(is_tick_abnormal, tick_msg)
        append_abnormal(is_trade_abnormal, trade_msg)

        if abnormal:
            msg.insert(0, "[on_risk_check:stop]")
            self.send_dingding(msg=msg)
            self.on_abnormal(msg)  # 触发异常处理，终止程序
            return True
        return False

    def check_widen_alerts(self, t_now, coin_account, usdt_account, msg):
        """
        检查 Widen Alert 条件并触发相应事件。
        """
        self.write_log(f"[check_widen_alerts:{t_now}:{coin_account.available}, {usdt_account.available},"
                       f" {self.widen_alerts_threshold[WidenAlertType.COIN_INVENTORY_INSUFFICIENT.value]}, "
                       f"{self.widen_alerts_threshold[WidenAlertType.USDT_INVENTORY_INSUFFICIENT.value]}]")
        previous_widen_alerts = self.widen_alerts.copy()

        def set_widen_alert(condition, alert_type, alert_msg):
            prev_value = self.widen_alerts[alert_type.value]  # 之前的值
            new_value = condition  # 计算的新值

            # 只有当 True/False 发生变化时才触发推送
            if prev_value != new_value:
                self.widen_alerts[alert_type.value] = new_value  # 更新值
                msg.append(alert_msg)

        set_widen_alert(
            coin_account.available < self.widen_alerts_threshold[WidenAlertType.COIN_INVENTORY_INSUFFICIENT.value],
            WidenAlertType.COIN_INVENTORY_INSUFFICIENT,
            f"insufficient coins to sell, currently available {coin_account.available}, "
            f"frozen {coin_account.frozen}, total {coin_account.total}"
        )
        set_widen_alert(
            usdt_account.available < self.widen_alerts_threshold[WidenAlertType.USDT_INVENTORY_INSUFFICIENT.value],
            WidenAlertType.USDT_INVENTORY_INSUFFICIENT,
            f"insufficient USDT to buy, currently available {usdt_account.available}, "
            f"frozen {usdt_account.frozen}, total {usdt_account.total}"
        )

        if self.latest_update_tick_time:
            set_widen_alert(
                t_now - self.latest_update_tick_time * 0.000001 > self.widen_alerts_threshold[
                    WidenAlertType.BBO_OUT_OF_DATE.value],
                WidenAlertType.BBO_OUT_OF_DATE,
                f"tick latest update time: {datetime.fromtimestamp(self.latest_update_tick_time * 0.000001)}"
            )

        if self.latest_update_trade_time:
            set_widen_alert(
                t_now - self.latest_update_trade_time * 0.000001 > self.widen_alerts_threshold[
                    WidenAlertType.PUBLIC_TRADE_OUT_OF_DATE.value],
                WidenAlertType.PUBLIC_TRADE_OUT_OF_DATE,
                f"trade latest update time: {datetime.fromtimestamp(self.latest_update_trade_time * 0.000001)}"
            )

        # 计算所有变化的字段
        changed_alerts = {
            key: (previous_widen_alerts[key], self.widen_alerts[key])
            for key in self.widen_alerts if previous_widen_alerts[key] != self.widen_alerts[key]
        }
        # 如果有变化，触发消息推送
        if changed_alerts:
            msg.insert(0, f"[on_risk_check：widen system]: {changed_alerts}")
            self.send_dingding(msg=msg)
            self.put_event()  # 触发事件给另一个程序处理

        # print(self.widen_alerts)

    def on_risk_check(self):

        t_now = time.time()
        msg = []

        coin_account: AccountData = self.inventory[self.symbol]
        usdt_account: AccountData = self.inventory["USDT"]

        # abnormal situation check
        if self.check_abnormal_conditions(t_now, coin_account, usdt_account, msg):
            return

        # Widen Alert check
        self.check_widen_alerts(t_now, coin_account, usdt_account, msg)

    def on_check_settings(self):

        strategies_setting = load_json(self.setting_filename)

        for strategy_name, strategy_config in strategies_setting.items():
            strategy_parameters = strategy_config["setting"]

            if strategy_name == self.maker_strategy_name:
                if self.maker_strategy_parameters != strategy_parameters:
                    self.write_log(
                        f"[on_check_setting:{strategy_name}], 当前parameters={self.taker_strategy_parameters}, "
                        f"修改后 parameters={strategy_parameters}")
                    changed = find_changed_values(self.maker_strategy_parameters, strategy_parameters)
                    self.maker_strategy_parameters = strategy_parameters
                    self.cta_engine.edit_strategy(strategy_name, strategy_parameters)
                    self.send_dingding(f"[on_parameters_change:{strategy_name}] {changed}")

            elif strategy_name == self.taker_strategy_name:
                if self.taker_strategy_parameters != strategy_parameters:
                    self.write_log(
                        f"[on_check_setting:{strategy_name}], 当前parameters={self.taker_strategy_parameters}, "
                        f"修改后 parameters={strategy_parameters}")
                    changed = find_changed_values(self.taker_strategy_parameters, strategy_parameters)
                    self.taker_strategy_parameters = strategy_parameters
                    self.cta_engine.edit_strategy(strategy_name, strategy_parameters)
                    self.send_dingding(f"[on_parameters_change:{strategy_name}] {changed}")

            elif strategy_name == self.risk_strategy_name:
                if self.risk_strategy_parameters != strategy_parameters:
                    self.write_log(
                        f"[on_check_setting:{strategy_name}], 当前parameters={self.risk_strategy_parameters}, "
                        f"修改后 parameters={strategy_parameters}")
                    changed = find_changed_values(self.risk_strategy_parameters, strategy_parameters)
                    self.risk_strategy_parameters = strategy_parameters
                    self.cta_engine.edit_strategy(strategy_name, strategy_parameters)
                    self.send_dingding(f"[on_parameters_change:{strategy_name}] {changed}")

    # def on_query_pending_order(self):
    #     for strategy in self.cta_engine.strategies.values():
    #         strategy.query_all_pending_order()

    def on_account(self, account: AccountData):
        self.inventory[account.currency] = account
        if account.currency == "USDT":
            self.monitor_info.usdt_left = account.available
            self.monitor_info.usdt_total = account.total
            self.monitor_info.buy_frozen = account.frozen
        else:
            self.monitor_info.coin_left = account.available
            self.monitor_info.coin_total = account.total
            self.monitor_info.sell_frozen = account.frozen

    def on_stop(self):
        """
        Callback when strategy is stopped.
        """
        self.write_log("策略停止")
